package cn.xzqwjw.taskmanager.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

/**
 * @author rush
 */
public class DateTimeUtil {

  private static final Date DATE_NOW = new Date();
  private static final int LENGTH10 = 10;
  private static final int LENGTH13 = 13;

  /**
   * 检查时间格式字符串是否是预先自定义的几种格式
   */
  public static boolean isWrongFormatString(String formatString) {
    boolean flagReturn = true;
    if (!StringUtils.hasText(formatString)) {
      return true;
    }
    // 定义一组常用的时间格式字符串
    String[] allowedFormatString = {
        "yyyyMM",
        "yyyyMMdd",
        "yyyyMMdd HH:mm:ss",
        "yyyy-MM-dd",
        "yyyy-MM-dd HH:mm:ss",
        "yyyy/MM/dd",
        "yyyy/MM/dd HH:mm:ss"
    };
    for (String format : allowedFormatString) {
      if (format.equals(formatString)) {
        flagReturn = false;
        break;
      }
    }
    return flagReturn;
  }


  public static boolean isDate(String dateString, String formatString) {
    if (isWrongFormatString(formatString)) {
      return false;
    }
    final DateFormat sdf = new SimpleDateFormat(formatString);
    try {
      sdf.parse(dateString);
    } catch (ParseException e) {
      return false;
    }
    return true;
  }

  public static boolean isDateTime(String datetimeString, String formatString) {
    if (isWrongFormatString(formatString)) {
      return false;
    }
    final DateFormat sdf = new SimpleDateFormat(formatString);
    try {
      sdf.parse(datetimeString);
    } catch (ParseException e) {
      return false;
    }
    return true;
  }

  /**
   * 得到当前日期时间的Calendar
   *
   * @return Calendar 当前日期时间的Calendar
   */
  public static Calendar getNowCalendar() {
    return Calendar.getInstance();
  }

  /**
   * 得到指定日期时间的Calendar
   *
   * @param date 指定的日期时间
   * @return Calendar 指定的日期时间的Calendar
   */
  public static Calendar getCalendarByDate(Date date) {
    Calendar calendar = getNowCalendar();
    calendar.setTime(date);
    return calendar;
  }

  /**
   * 得到指定时间的年的字符串
   *
   * @param date 指定的Date格式的日期时间
   * @return 指定时间的年的字符串
   */
  public static String getYearString(Date date) {
    return getCalendarByDate(date).get(Calendar.YEAR) + "";
  }

  /**
   * 得到指定时间的月的字符串，小于10补上 0
   *
   * @param date 指定的Date格式的日期时间
   * @return 指定时间的月的字符串，如果小于10则为“02”
   */
  public static String getMonthString(Date date) {
    int month = getCalendarByDate(date).get(Calendar.MONTH) + 1;
    return (month < 10) ? "0" + month : "" + month;
  }

  /**
   * 得到指定时间的日的字符串，小于10补上 0
   *
   * @return 指定时间的日的字符串，如果小于10则为“02”
   */
  public static String getDayString(Date date) {
    int day = getCalendarByDate(date).get(Calendar.DAY_OF_MONTH);
    return (day < 10) ? "0" + day : "" + day;
  }

  /**
   * 得到指定时间的小时的字符串，小于10补上 0
   *
   * @return 指定时间的小时的字符串，如果小于10则补上 0
   */
  public static String getHourString(Date date) {
    int hour = getCalendarByDate(date).get(Calendar.HOUR_OF_DAY);
    return (hour < 10) ? "0" + hour : "" + hour;
  }

  /**
   * 得到指定时间的分钟的字符串，小于10补上 0
   *
   * @return 指定时间的分钟的字符串，如果小于10则补上 0
   */
  public static String getMinuteString(Date date) {
    int minute = getCalendarByDate(date).get(Calendar.MINUTE);
    return (minute < 10) ? "0" + minute : "" + minute;
  }

  /**
   * 得到指定时间的秒的字符串，小于10补上 0
   *
   * @return 指定时间的秒的字符串，如果小于10则补上 0
   */
  public static String getSecondString(Date date) {
    int second = getCalendarByDate(date).get(Calendar.SECOND);
    return (second < 10) ? "0" + second : "" + second;
  }

  /**
   * 得到指定时间的毫秒的字符串，小于10补上 0
   *
   * @return 指定时间的毫秒的字符串，如果小于10则补上 0
   */
  public static String getMilliSecondString(Date date) {
    String result = "";
    int milliSecond = getCalendarByDate(date).get(Calendar.MILLISECOND);
    if (milliSecond < 10) {
      result = "00" + milliSecond;
    } else if (milliSecond < 100) {
      result = "0" + milliSecond;
    } else {
      result = "" + milliSecond;
    }
    return result;
  }

  // ==================== String，Long，Date 三种类型互转

  /**
   * 将字符串转换成时间
   *
   * @param datetimeString 要转换成时间的字符串
   * @param formatString   字符串的时间格式
   * @return 转换后的时间
   */
  public static Date string2Date(String datetimeString, String formatString) {
    if (!StringUtils.hasText(datetimeString)) {
      return null;
    }
    if (!StringUtils.hasText(formatString)) {
      formatString = "yyyy-MM-dd HH:mm:ss";
    } else if (isWrongFormatString(formatString)) {
      return null;
    }
    SimpleDateFormat sdf = new SimpleDateFormat(formatString);
    try {
      return sdf.parse(datetimeString);
    } catch (ParseException e) {
      return null;
    }
  }

  /**
   * 将按某时间格式的时间字符串转换为13位时间戳，java只有13位时间戳
   *
   * @param datetimeString 要转换的日期时间字符串
   * @param formatString   时间格式字符串
   * @return Long格式的时间戳
   */
  public static Long string2Stamp(String datetimeString, String formatString) {
    if (!StringUtils.hasLength(datetimeString)) {
      return 0L;
    }
    if (isWrongFormatString(formatString)) {
      return 0L;
    }
    SimpleDateFormat sdf = new SimpleDateFormat(formatString);
    try {
      return sdf.parse(datetimeString).getTime();
    } catch (ParseException e) {
      e.printStackTrace();
      return 0L;
    }
  }

  /**
   * 将时间转换为指定格式的字符串
   *
   * @param date 要转换的时间字符串
   * @return yyyy-MM-dd HH:mm:ss固定格式的字符串
   */
  public static String date2String(Date date, String formatString) {
    if (Objects.isNull(date)) {
      return "";
    }
    if (isWrongFormatString(formatString)) {
      return "";
    }
    SimpleDateFormat sdf = new SimpleDateFormat(formatString);
    return sdf.format(date);
  }

  /**
   * 将时间转换为时间戳
   *
   * @param date 要转换的时间
   * @return Long格式的时间戳
   */
  public static Long date2Stamp(Date date) {
    String temp = date2String(date, "yyyy-MM-dd HH:mm:ss");
    return string2Stamp(temp, "yyyy-MM-dd HH:mm:ss");
  }

  /**
   * 将时间戳按某时间格式转换为时间字符串
   *
   * @param datetimeStamp 要转换的时间戳
   * @param formatString  时间格式字符串
   * @return 时间格式字符串
   */
  public static String stamp2String(Long datetimeStamp, String formatString) {
    String temp = transStamp10To13(datetimeStamp.toString());
    if (!StringUtils.hasLength(temp)) {
      return "";
    }
    datetimeStamp = Long.parseLong(temp);
    if (isWrongFormatString(formatString)) {
      return "";
    }
    SimpleDateFormat sdf = new SimpleDateFormat(formatString);
    return sdf.format(new Date(datetimeStamp));
  }

  /**
   * 将时间戳转换为时间
   *
   * @param datetimeStamp 要转换的时间戳
   * @return Date类型的时间
   */
  public static Date stamp2Date(Long datetimeStamp) {
    // 先将时间戳转成字符串
    String temp = stamp2String(datetimeStamp, "yyyy-MM-dd HH:mm:ss");
    // 再将字符串转成时间
    return string2Date(temp, "yyyy-MM-dd HH:mm:ss");
  }

  // ==================== String，Long，LocalDate 三种类型互转

  public static LocalDate string2Ld(String dateString, String formatString) {
    if (!StringUtils.hasLength(dateString) || isWrongFormatString(formatString)) {
      return null;
    }
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(formatString);
    try {
      return LocalDate.parse(dateString, dtf);
    } catch (Exception e) {
      return null;
    }
  }

  public static LocalDateTime string2Ldt(String dtString, String formatString) {
    if (!StringUtils.hasLength(dtString) || isWrongFormatString(formatString)) {
      return null;
    }
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(formatString);
    try {
      return LocalDateTime.parse(dtString, dtf);
    } catch (Exception e) {
      return null;
    }
  }

  public static String ldt2String(LocalDateTime ldt, String formatString) {
    if (Objects.isNull(ldt) || isWrongFormatString(formatString)) {
      return "";
    }
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(formatString);
    try {
      return dtf.format(ldt);
    } catch (DateTimeException e) {
      return "";
    }
  }

  // ==================== 一些常用方法

  /**
   * 将 LocalDateTime 转成 Date
   *
   * @param ldt 要转换的 LocalDateTime
   * @return Date 类型的时间
   */
  public static Date ldt2Dt(LocalDateTime ldt) {
    return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
  }

  /**
   * 将 Date 转成 LocalDateTime
   *
   * @param date 要转换的 Date
   * @return LocalDateTime 类型的时间
   */
  public static LocalDateTime dt2Ldt(Date date) {
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
  }

  /**
   * 得到当前日期时间的字符串，格式为"yyyy-MM-dd HH:mm:ss"
   *
   * @return String 当前日期时间的字符串
   */
  public static String getNowString() {
    return date2String(DATE_NOW, "yyyy-MM-dd HH:mm:ss");
  }

  /**
   * 得到当前日期时间的时间戳
   *
   * @return String 当前日期时间的时间戳
   */
  public static Long getNowStamp13() {
    return date2Stamp(DATE_NOW);
  }

  /**
   * 得到当前日期时间的10位时间戳
   *
   * @return String 当前日期时间的时间戳
   */
  public static Long getNowStamp10() {
    return transStamp13To10(getNowStamp13());
  }

  /**
   * 将时间格式的时间转成年月日组成的数字格式
   * 此方法常用于排行榜开始时间和结束时间的比较
   * 因为ilas数据库里时间存放的是年月日组成的数字格式
   *
   * @param date Date格式的时间
   * @return 年月日组成的整形数字
   */
  public static Integer date2Ymd(Date date) {
    String yearString = getYearString(date);
    String monthString = getMonthString(date);
    String dayString = getDayString(date);
    return Integer.parseInt(yearString + monthString + dayString);
  }

  /**
   * java的date默认精度是毫秒，也就是说生成的时间戳就是13位的
   * 而像c++或者php生成的时间戳默认就是10位的，因为其精度是秒
   * 第一种：通过substring方法，将13位的时间戳最后三位数字截取
   * 第二种：将13位时间戳除以1000取整。
   *
   * @param stampString 10或13位的时间戳字符串
   * @return 13位的java格式的时间戳字符串
   */
  public static String transStamp10To13(String stampString) {
    if (!StringUtils.hasLength(stampString)) {
      return "";
    }
    if (stampString.length() == LENGTH10) {
      return stampString + "000";
    } else if (stampString.length() == LENGTH13) {
      return stampString;
    } else {
      return "";
    }
  }

  /**
   * java的date默认精度是毫秒，也就是说生成的时间戳是13位的
   * oracle存的时间戳是10位的
   *
   * @param stamp 时间戳
   * @return 13位的java格式的时间戳字符串
   */
  public static Long transStamp13To10(Long stamp) {
    if (Objects.isNull(stamp)) {
      return 0L;
    }
    int length = stamp.toString().length();
    if (length == LENGTH13) {
      return Long.parseLong(stamp.toString().substring(0, 10));
    } else if (length == LENGTH10) {
      return stamp;
    } else {
      return 0L;
    }
  }

  // ==================== 根据特定日期获取当日的开始时间与结束时间

  /**
   * 获取指定日期时间的当日的开始时间
   */
  public static Date getDayBegin(Date dateTime) {
    if (Objects.isNull(dateTime)) {
      return null;
    }
    Calendar cal = getNowCalendar();
    cal.setTime(dateTime);
    cal = setSolidTime(cal, 0, 0, 0, 0);
    assert cal != null;
    return cal.getTime();
  }

  /**
   * 获取指定日期时间的当日的结束时间
   */
  public static Date getDayEnd(Date dateTime) {
    if (Objects.isNull(dateTime)) {
      return null;
    }
    Calendar cal = getNowCalendar();
    cal.setTime(dateTime);
    cal = setSolidTime(cal, 23, 59, 59, 999);
    assert cal != null;
    return cal.getTime();
  }

  /**
   * 获取指定日期时间的当月的开始时间("2021-08-1 0:0:0.0")
   */
  public static Date getMonthBegin(Date dateTime) {
    if (Objects.isNull(dateTime)) {
      return null;
    }
    Calendar cal = getNowCalendar();
    cal.setTime(dateTime);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal = setSolidTime(cal, 0, 0, 0, 0);
    assert cal != null;
    return cal.getTime();
  }

  /**
   * 获取指定日期时间的当月的结束时间("2021-08-31 23:59:59.999")
   */
  public static Date getMonthEnd(Date dateTime) {
    if (Objects.isNull(dateTime)) {
      return null;
    }
    Calendar cal = getNowCalendar();
    cal.setTime(dateTime);
    // 设置为当月最后一天
    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
    cal = setSolidTime(cal, 23, 59, 59, 999);
    assert cal != null;
    return cal.getTime();
  }

  /**
   * 获取指定日期时间的当年的开始时间
   */
  public static Date getYearBegin(Date dateTime) {
    if (Objects.isNull(dateTime)) {
      return null;
    }
    Calendar cal = getNowCalendar();
    cal.setTime(dateTime);
    // 设置月和日都为1，即为开始时间
    // 注：月份是从0开始；日中0表示上个月最后一天，1表示本月开始第一天
    cal.set(Calendar.MONTH, 0);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal = setSolidTime(cal, 0, 0, 0, 0);
    assert cal != null;
    return cal.getTime();
  }

  /**
   * 获取指定日期时间的当年的结束时间
   */
  public static Date getYearEnd(Date dateTime) {
    if (Objects.isNull(dateTime)) {
      return null;
    }
    Calendar cal = getNowCalendar();
    cal.setTime(dateTime);
    // 设置月为12，月份从0开始
    cal.set(Calendar.MONTH, 11);
    // 设置为当月最后一天
    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
    cal = setSolidTime(cal, 23, 59, 59, 999);
    assert cal != null;
    return cal.getTime();
  }

  /**
   * 根据指定的年和月得到该月的起始日期字符串
   */
  public static String[] getStartEndByYearMonth(Integer year, Integer month) {
    String dateString = year + "" + (month < 10 ? "0" + month : month);
    Date dateTemp = string2Date(dateString, "yyyyMM");
    Date dateStart = getMonthBegin(dateTemp);
    Date dateEnd = getMonthEnd(dateTemp);
    String dateStartString = date2String(dateStart, "yyyyMMdd");
    String dateEndString = date2String(dateEnd, "yyyyMMdd");
    return new String[]{dateStartString, dateEndString};
  }

  // ==================== 获取时间差 ====================

  /**
   * 得到第2个入参日期和第1个入参日期之间相差的天数（比如20181207和20210813）
   * 1、将Date格式化为只有年、月、日的形式，返回String。
   * 2、将此String再转化为Date(此时时、分、秒就成为了00:00:00）
   * 3、调用getTime()方法返回Date的1970的毫秒表示。
   * 4、将两个日期的毫秒相减，然后除以一天有（24*60*60*1000）毫秒，得到日期差。
   */
  public static int getIntervalDays(Date startDate, Date endDate) {
    if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
      return 0;
    }
    // 将Date格式化为只有年、月、日的形式，返回String
    // 再将此String再转化为Date(此时时、分、秒就成为了00:00:00）
    String tempString = date2String(startDate, "yyyyMMdd");
    startDate = string2Date(tempString, "yyyyMMdd");
    tempString = date2String(endDate, "yyyyMMdd");
    endDate = string2Date(tempString, "yyyyMMdd");
    if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
      return 0;
    }
    return (int) ((endDate.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1000));
  }

  /**
   * 时间比较，前时间小于后时间（前时间在前）则返回值小于0，
   * 前时间大于后时间（前时间在后）则返回值大于0，相等则返回0
   */
  public static int compareTime(Date t1, Date t2) {
    return Long.compare(t1.getTime(), t2.getTime());
  }

  /**
   * 判断现在的时间和指定闭馆时间的顺序，-1表示之前，0表示等于，1表示之后
   * 周五、六延长到晚8点，原来是17点
   * 如果在今天20：10之前，则不需要更新
   * 如果在今天20：10之后，则需要更新
   * 万力分馆闭馆时间设为22：00
   *
   * @return -1表示现在的时间在指定时间之前，0表示等于，1表示之后
   */
  public static int compareOverTime(int overHour, int overMinute) {
    Calendar cal = getNowCalendar();
    cal.setTime(new Date());
    // 得到现在的日期时间
    Date t1 = cal.getTime();
    // 得到固定时间 20:10:00.000
    cal = setSolidTime(cal, overHour, overMinute, 0, 0);
    assert cal != null;
    Date t2 = cal.getTime();
    return compareTime(t1, t2);
  }

  public static Calendar setSolidDate(Calendar cal, int year, int month, int day) {
    if (month < 1 || month > 12 || day < 1 || day > 31) {
      return null;
    }
    // 设置月和日都为1，即为开始时间
    // 注：月份是从0开始；日中0表示上个月最后一天，1表示本月开始第一天
    cal.set(Calendar.YEAR, year);
    cal.set(Calendar.MONTH, month);
    cal.set(Calendar.DAY_OF_MONTH, day);
    return cal;
  }

  /**
   * 将某个calendar设置到固定的时间点，日期不做更改
   **/
  public static Calendar setSolidTime(Calendar cal,
                                      int hour,
                                      int minute,
                                      int second,
                                      int milliSecond) {
    if (hour < 0 || hour > 23 || minute < 0 || minute > 59 ||
        second < 0 || second > 59 || milliSecond < 0 || milliSecond > 999) {
      return null;
    }
    cal.set(Calendar.HOUR_OF_DAY, hour);
    cal.set(Calendar.MINUTE, minute);
    cal.set(Calendar.SECOND, second);
    cal.set(Calendar.MILLISECOND, milliSecond);
    return cal;
  }

  /* 直接读取 yml 配置文件里的定义的变量 */
  @Value("${spring.aop.maxExecuteTime}")
  private static long MAX_EXECUTE_TIME;

  /**
   * 打印方法执行耗时的信息，如果超过了一定的时间，才打印
   *
   * @param methodName: 方法名称
   * @param startTime:  开始时间
   * @param endTime:    结束时间
   **/
  public static void printExecTime(String methodName, long startTime, long endTime) {
    long diffTime = endTime - startTime;
    if (diffTime > MAX_EXECUTE_TIME) {
      System.out.println(methodName + " 方法执行耗时：" + diffTime + " ms");
    }
    // TODO 可以集成redis，将每个controller的执行时间追加到redis中，再用定时每周一次同步到库中
  }

}
